Skip to content

feat(i2c): Add c_board I2C2 transport support#47

Closed
gqsdjhh wants to merge 1 commit into
mainfrom
dev/c_board_i2c
Closed

feat(i2c): Add c_board I2C2 transport support#47
gqsdjhh wants to merge 1 commit into
mainfrom
dev/c_board_i2c

Conversation

@gqsdjhh
Copy link
Copy Markdown

@gqsdjhh gqsdjhh commented Apr 19, 2026

Summary

This PR adds end-to-end I2C transport support for c_board.

It introduces the I2C wire format in the shared protocol layer, exposes host-side I2C APIs for CBoard, and implements the firmware-side transport on c_board by mapping the protocol-facing logical I2C0
channel to STM32 I2C2 with DMA-backed transfers.

What Changed

1. Shared protocol layer

  • Add shared I2C protocol constants in core/include/librmcs/protocol/i2c.hpp
  • Extend public data definitions with:
    • I2cDataView
    • I2cReadConfigView
    • I2cErrorView
  • Extend DataCallback to report structured I2C errors instead of only a slave address
  • Add I2cHeader protocol layout with:
    • payload type
    • optional register flag
    • read/write error flag
    • 7-bit slave address
    • 9-bit payload/read length
  • Add serializer support for:
    • I2C write
    • I2C read request
    • I2C read result
    • I2C error
  • Add deserializer support for the same I2C frame types, including validation for:
    • invalid address width
    • unexpected error flag combinations
    • zero-length write/read-request frames

2. Host-side support

  • Extend host::protocol::Handler::PacketBuilder with I2C write/read/read-result helpers
  • Add I2C handling to the host deserialization path
  • Improve host-side I2C error reporting by preserving:
    • read vs write direction
    • register presence/address
    • requested or transferred data length
  • Extract reusable single-channel I2C host helpers into agent/detail/i2c0_common.hpp
  • Expose I2C0 packet-builder and callback support from agent::CBoard

3. c_board firmware transport

  • Add a dedicated firmware/c_board/app/src/i2c/ module
  • Implement queued I2C requests for:
    • write transfers
    • read requests
  • Use STM32 HAL DMA APIs for both write and read paths
  • Add completion and error callback handling through HAL I2C callbacks
  • Add transfer timeout recovery and peripheral reinitialization on failure
  • Buffer uplink read results and I2C error reports so they can be sent through USB
  • Call the new I2C module from the main app loop

4. USB downlink/uplink integration

  • Extend the c_board USB vendor deserializer callback to dispatch:
    • I2C write frames
    • I2C read request frames
  • Keep uplink-only I2C frame types ignored on downlink
  • Adjust interrupt-safe transmit buffer unlock behavior so queued uplinks are not dropped before transmission resumes

5. CubeMX / BSP integration

  • Enable HAL I2C support in CubeMX-generated config
  • Add CubeMX-generated I2C2 peripheral files
  • Configure:
    • I2C2
    • DMA RX/TX streams
    • I2C IRQs
    • PF0/PF1 pin mux for SDA/SCL
  • Update the CubeMX project metadata (rmcs_slave.ioc) accordingly

Design Notes

  • The protocol-facing channel remains I2C0 in shared/host APIs.
  • On c_board, that logical channel is implemented by STM32 I2C2.
  • This keeps the public protocol naming consistent across boards while allowing board-specific hardware mapping internally.

Scope

This PR is focused on c_board I2C transport support and the shared protocol/host pieces required for it.

It does not introduce a new public multi-I2C board model yet; it adds the first concrete c_board I2C transport path on top of the shared protocol.

I2C2传输支持

本PR为c_board添加了端到端的I2C传输支持,跨越共享协议、主机端、固件和USB/CubeMX集成四个领域。

协议层

新增I2C协议定义 (core/include/librmcs/protocol/i2c.hpp)

  • 定义I2C数据长度为9比特,最大数据长度为511字节

数据接口扩展 (core/include/librmcs/data/datas.hpp)

  • 新增DataId::kI2c0 = 16标识符
  • 新增三个I2C数据视图结构体:I2cDataView(从地址、数据载荷、可选寄存器)、I2cReadConfigView(从地址、读长度、可选寄存器)、I2cErrorView(从地址、数据长度、可选寄存器、读写标志)
  • DataCallback接口新增两个虚函数:i2c_receive_callback()i2c_error_callback()

协议编码 (core/src/protocol/protocol.hpp, serializer.hpp, deserializer.cpp)

  • 新增I2cHeader位域结构体,包含载荷类型选择器、寄存器标志、错误标志、7比特从地址和9比特数据长度
  • 四种载荷类型:写入(kWrite)、读请求(kReadRequest)、读结果(kReadResult)、错误(kError)
  • 序列化器新增五个公共方法:write_i2c_write()write_i2c_read_config()write_i2c_read_result()write_i2c_error()(两个重载)
  • 反序列化器新增协程process_i2c_field()处理I2C帧解析与验证,包括从地址范围检查、长度验证、标志组合验证等

主机端

代理接口 (host/include/librmcs/agent/c_board.hpp)

  • CBoard现继承自detail::SingleI2c0DataCallback替代直接继承DataCallback
  • 嵌套PacketBuilder继承自detail::I2c0PacketBuilderMixin<PacketBuilder>

I2C0工具库 (host/include/librmcs/agent/detail/i2c0_common.hpp)

  • 新增SingleI2c0DataCallback实现,针对DataId::kI2c0路由I2C数据和错误回调
  • 新增模板I2c0PacketBuilderMixin<Derived>提供i2c0_write()i2c0_read()方法,含参数验证(从地址≤0x7F,数据长度验证)

协议处理器 (host/include/librmcs/protocol/handler.hpp, src/protocol/handler.cpp)

  • PacketBuilder新增三个公共方法:write_i2c()write_i2c_read_config()write_i2c_read_result()
  • 新增process_result_strict辅助函数改进错误处理(kBadAlloc现视为失败)

c_board固件

I2C驱动 (firmware/c_board/app/src/i2c/i2c.hppi2c.cpp)

  • 新增I2c类实现DMA驱动的排队写/读传输,单例i2c0绑定到STM32 I2C2
  • 支持写请求和读配置的下链处理(handle_downlink_write()handle_downlink_read_config()
  • 队列管理、完成回调(tx_complete_callback()rx_complete_callback())、错误处理和恢复
  • 读结果和错误上链缓冲与发布机制(当USB序列化不可用时)
  • STM32 HAL I2C中断回调映射(HAL_I2C_MasterTxCpltCallback等)

应用集成 (firmware/c_board/app/src/app.cpp)

  • 添加I2C2硬件初始化和主循环更新调用

USB处理 (firmware/c_board/app/src/usb/vendor.hpp, interrupt_safe_buffer.hpp)

  • USB反序列化器新增I2C写和读配置的下链处理,分发至i2c::i2c0
  • 读结果和错误帧在下链被忽略
  • 调整缓冲区解锁API:try_unlock_and_clear()更名为try_unlock(),移除清除行为以防丢弃队列中的上链消息

硬件和BSP配置

CubeMX配置 (firmware/c_board/bsp/cubemx/)

  • 启用I2C HAL模块(HAL_I2C_MODULE_ENABLED
  • 新增I2C2外设初始化(MX_I2C2_Init())、MSP初始化/反初始化
  • 配置PF0/PF1为I2C2_SDA/I2C2_SCL(开漏模式)
  • 配置DMA1 Stream2/7用于I2C2 RX/TX,优先级(1,0)
  • 新增I2C2事件和错误中断处理器
  • 更新CMake配置以编译I2C HAL驱动源文件

GPIO和DMA

  • 启用GPIOF时钟以支持PF0/PF1引脚

设计说明:协议层I2C通道标识为I2C0,在c_board硬件上映射到STM32 I2C2外设。

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 19, 2026

总体概览

该PR为机器人控制系统添加了完整的I2C(Inter-Integrated Circuit)通信支持,涵盖跨越核心协议层、固件硬件驱动和主机代理的集成实现。包括新增数据结构、序列化/反序列化流程、STM32硬件初始化和固件驱动等内容。

变更清单

分类 / 文件 摘要
核心协议数据定义
core/include/librmcs/data/datas.hpp, core/include/librmcs/protocol/i2c.hpp
新增 DataId::kI2c0 枚举值、I2cDataViewI2cReadConfigViewI2cErrorView 结构体,向 DataCallback 接口添加 I2C 回调虚函数。定义 I2C 协议常量 kI2cDataLengthBits=9kI2cMaxDataLength
协议序列化/反序列化实现
core/src/protocol/protocol.hpp, core/src/protocol/serializer.hpp, core/src/protocol/deserializer.hpp, core/src/protocol/deserializer.cpp
新增 I2cHeader 位域结构体定义,包含有效载荷类型、寄存器标志、错误标志、从地址和数据长度。为 Serializer 添加 write_i2c_* 系列方法实现 I2C 消息编码。为 Deserializer 添加 process_i2c_field() 协程实现 I2C 消息解码及回调分发。
固件硬件初始化
firmware/c_board/bsp/cubemx/Core/Inc/..., firmware/c_board/bsp/cubemx/Core/Src/i2c.c, firmware/c_board/bsp/cubemx/Core/Src/stm32f4xx_it.c, firmware/c_board/bsp/cubemx/rmcs_slave.ioc, firmware/c_board/bsp/cubemx/cmake/...
添加 CubeMX I2C2 外设配置(PF0/PF1 引脚、100kHz 速率、DMA 流配置)。实现 MX_I2C2_Init()HAL_I2C_MspInit()、中断处理程序及相应的 DMA 中断配置。
固件 I2C 驱动实现
firmware/c_board/app/src/i2c/i2c.hpp, firmware/c_board/app/src/i2c/i2c.cpp
实现 I2c 类封装硬件 HAL,支持请求队列、DMA 传输、中断回调、超时检测和错误恢复。集成 update() 到应用主循环。
固件应用集成
firmware/c_board/app/src/app.cpp, firmware/c_board/app/src/usb/interrupt_safe_buffer.hpp, firmware/c_board/app/src/usb/vendor.hpp
初始化 I2C2 外设和软件实例,在主循环中调用 i2c0->update()。实现 DeserializeCallback I2C 回调对接。将 try_unlock_and_clear() 改名为 try_unlock(),删除自动清空行为。
主机协议处理器
host/include/librmcs/protocol/handler.hpp, host/src/protocol/handler.cpp
PacketBuilder 添加 write_i2c()write_i2c_read_config()write_i2c_read_result() 公共接口方法。实现 I2C 消息序列化和错误处理回调。
主机代理绑定
host/include/librmcs/agent/detail/i2c0_common.hpp, host/include/librmcs/agent/c_board.hpp
新增 SingleI2c0DataCallbackI2c0PacketBuilderMixin 模板类,为应用层提供 I2C0 特定的接收和错误回调入口点。重构 CBoard 继承链以使用新的 mixin 基类。

代码审查难度评估

🎯 4 (复杂) | ⏱️ ~60 分钟

说明:本 PR 涵盖多个分层次的变更(协议、硬件驱动、固件、主机集成),涉及复杂的异步I/O流程、中断处理、DMA管理、错误恢复逻辑和跨边界的回调机制。序列化/反序列化逻辑、固件驱动的请求队列和超时管理、以及主机端的模板/继承链都需要仔细审查以确保正确性。

可能相关的PR

🐰 I2C 的小径现出来,
从硅芯数据流向外
DMA 翩翩舞队列,
主从握手成一对,
代理智慧路通畅!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR标题清晰准确地反映了主要变更:在c_board上添加I2C2传输支持,这与整个变更集的核心目标完全一致。

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dev/c_board_i2c

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
core/include/librmcs/protocol/i2c.hpp (1)

8-10: 建议加一条防御性 static_assert

kI2cMaxDataLength 通过 static_cast<std::uint16_t>((1U << kI2cDataLengthBits) - 1U) 计算,当前 kI2cDataLengthBits = 9 时结果为 511,正确。但若将来有人将位宽改为 > 16,强制转换会静默截断导致协议常量错误。建议增加一个编译期断言以防止此类回归:

♻️ 建议的小幅加固
 inline constexpr std::size_t kI2cDataLengthBits = 9;
+static_assert(kI2cDataLengthBits < 16,
+              "kI2cDataLengthBits must fit in uint16_t without truncation");
 inline constexpr std::uint16_t kI2cMaxDataLength =
     static_cast<std::uint16_t>((1U << kI2cDataLengthBits) - 1U);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core/include/librmcs/protocol/i2c.hpp` around lines 8 - 10, Add a
compile-time guard to prevent silent truncation of kI2cMaxDataLength if
kI2cDataLengthBits is increased: insert a static_assert that kI2cDataLengthBits
is <= std::numeric_limits<std::uint16_t>::digits (and optionally > 0) before the
kI2cMaxDataLength definition so the expression static_cast<std::uint16_t>((1U <<
kI2cDataLengthBits) - 1U) cannot overflow/truncate; reference the constants
kI2cDataLengthBits and kI2cMaxDataLength when adding the assertion.
firmware/c_board/app/src/i2c/i2c.hpp (2)

175-183: PendingUplink 对错误条目保留了整块 kMaxDataLength payload,内存浪费可观

pending_uplinks_kPendingUplinkQueueSize = 32 条固定大小的 PendingUplink,每条内含 alignas(uint32_t) std::array<std::byte, kMaxDataLength> payload{}。当 kMaxDataLength 等于协议允许的最大帧长度(看起来是 512 字节),即使一条队列中全部只是错误上报(不携带 payload),也要占用约 16 KB RAM。对 F4 级 MCU 这是相当大的一块。

可选改进(按影响由小到大):

  • kReadResultkError 拆成两条独立队列,错误队列只存元数据;
  • 或把读结果的 payload 单独放进一个小池(如 4~8 个 kMaxDataLength 槽),条目用 chunk 索引引用,类似现在的 payload_chunks_ 设计;
  • 或降低 kPendingUplinkQueueSize(32 条对错误/读结果积压而言偏多)。

此为可选优化,不影响功能正确性。

Also applies to: 682-682

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@firmware/c_board/app/src/i2c/i2c.hpp` around lines 175 - 183, PendingUplink
currently embeds a full kMaxDataLength payload causing large RAM use for
error-only entries; split uplink types into a small metadata-only ErrorUplink
and a ReadUplink that references payload storage: create a new lightweight
struct (e.g. ErrorUplink) containing
type/slave_address/data_length/has_register/reg_address/is_read without the
large payload, change pending_uplinks_ usage to maintain two queues (one for
ErrorUplink, one for ReadUplink or keep pending_uplinks_ only for reads), and
implement a small fixed-size payload pool (reuse existing payload_chunks_ design
or add a payload_chunks_ array and have ReadUplink store a chunk index and
actual length) so actual kMaxDataLength buffers are only allocated in the pool;
update enqueue/dequeue logic around functions that push/pop PendingUplink to use
the new structs and pool.

511-531: InterruptLockGuard 作用域内执行可达 kMaxDataLength 字节的 memcpy

第 525 行在关中断状态下把整段 payload 拷进 pending 槽。当 kMaxDataLength 取最大值时,这段拷贝会把整个系统的中断响应拖后数个微秒。

建议把 payload 拷贝挪出锁外:先在锁内分配/保留一个条目槽(占位并推进 head/count),记录目标槽指针,锁外再 memcpy payload,再在锁内将条目置为"已就绪"供 try_flush_uplink 消费。或者使用单生产者-单消费者的 ring,head 由 ISR 推进、tail 由主循环推进,拷贝本身不需关中断。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@firmware/c_board/app/src/i2c/i2c.hpp` around lines 511 - 531,
enqueue_read_result_uplink holds InterruptLockGuard while doing a potentially
large memcpy of up to kMaxDataLength bytes into pending_uplinks_, which extends
interrupt-disabled time; fix by reserving a slot inside the lock (use
pending_uplink_head_, pending_uplink_count_ and advance_pending_uplink_index to
increment head/count and obtain the target entry index or a pointer), release
the lock, perform std::memcpy to that target entry's payload outside the
InterruptLockGuard, then re-enter the lock and mark the entry fields (type,
slave_address, data_length, has_register, reg_address or a ready flag) so
try_flush_uplink can consume it; alternatively implement
single-producer-single-consumer ring semantics where ISR advances head and main
loop advances tail so the copy does not need interrupts disabled.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@core/src/protocol/deserializer.hpp`:
- Around line 44-52: Add no-op overrides for the four missing I2C callbacks in
the Vendor class so it fulfills the DeserializeCallback interface: implement
i2c_write_deserialized_callback(FieldId id, const data::I2cDataView& data)
override, i2c_read_config_deserialized_callback(FieldId id, const
data::I2cReadConfigView& data) override,
i2c_read_result_deserialized_callback(FieldId id, const data::I2cDataView& data)
override, and i2c_error_deserialized_callback(FieldId id, const
data::I2cErrorView& data) override in
firmware/rmcs_board/app/src/usb/vendor.hpp; each should have an empty body
(no-op), similar to existing rmcs_board GPIO/accelerometer callbacks, to satisfy
the pure-virtual methods in DeserializeCallback and unblock compilation.

In `@firmware/c_board/app/src/i2c/i2c.hpp`:
- Around line 412-419: recover_active_request_locked currently performs heavy
HAL operations (abort_dma_if_enabled, HAL_I2C_DeInit/HAL_I2C_Init) while holding
InterruptLockGuard, which blocks other ISRs; change it to only perform quick
locked updates: set recovery_pending_ = true, set internal state to Idle,
clear_stale_irq_sources_locked(), and call finish_active_request_locked() — do
not call abort_dma_if_enabled or HAL_I2C_DeInit/Init inside the lock. Move the
actual DMA abort and HAL reinit into the non-ISR path: have update() perform
abort_dma_if_enabled and HAL_I2C_DeInit/HAL_I2C_Init when recovery_pending_ is
true (mirroring error_callback's deferred recovery pattern at lines ~124-128).
Also modify callers (recover_active_request, recover_timed_out_transfer,
consume_deferred_recovery and the try_start_next_request failure branch) to set
recovery_pending_ instead of performing immediate HAL reinit so all heavy HAL
work occurs unlocked in update().

---

Nitpick comments:
In `@core/include/librmcs/protocol/i2c.hpp`:
- Around line 8-10: Add a compile-time guard to prevent silent truncation of
kI2cMaxDataLength if kI2cDataLengthBits is increased: insert a static_assert
that kI2cDataLengthBits is <= std::numeric_limits<std::uint16_t>::digits (and
optionally > 0) before the kI2cMaxDataLength definition so the expression
static_cast<std::uint16_t>((1U << kI2cDataLengthBits) - 1U) cannot
overflow/truncate; reference the constants kI2cDataLengthBits and
kI2cMaxDataLength when adding the assertion.

In `@firmware/c_board/app/src/i2c/i2c.hpp`:
- Around line 175-183: PendingUplink currently embeds a full kMaxDataLength
payload causing large RAM use for error-only entries; split uplink types into a
small metadata-only ErrorUplink and a ReadUplink that references payload
storage: create a new lightweight struct (e.g. ErrorUplink) containing
type/slave_address/data_length/has_register/reg_address/is_read without the
large payload, change pending_uplinks_ usage to maintain two queues (one for
ErrorUplink, one for ReadUplink or keep pending_uplinks_ only for reads), and
implement a small fixed-size payload pool (reuse existing payload_chunks_ design
or add a payload_chunks_ array and have ReadUplink store a chunk index and
actual length) so actual kMaxDataLength buffers are only allocated in the pool;
update enqueue/dequeue logic around functions that push/pop PendingUplink to use
the new structs and pool.
- Around line 511-531: enqueue_read_result_uplink holds InterruptLockGuard while
doing a potentially large memcpy of up to kMaxDataLength bytes into
pending_uplinks_, which extends interrupt-disabled time; fix by reserving a slot
inside the lock (use pending_uplink_head_, pending_uplink_count_ and
advance_pending_uplink_index to increment head/count and obtain the target entry
index or a pointer), release the lock, perform std::memcpy to that target
entry's payload outside the InterruptLockGuard, then re-enter the lock and mark
the entry fields (type, slave_address, data_length, has_register, reg_address or
a ready flag) so try_flush_uplink can consume it; alternatively implement
single-producer-single-consumer ring semantics where ISR advances head and main
loop advances tail so the copy does not need interrupts disabled.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 72a1c41d-62bf-49f3-a7d9-58760f13cde5

📥 Commits

Reviewing files that changed from the base of the PR and between 9a1bda6 and 6867cc3.

📒 Files selected for processing (25)
  • core/include/librmcs/data/datas.hpp
  • core/include/librmcs/protocol/i2c.hpp
  • core/src/protocol/deserializer.cpp
  • core/src/protocol/deserializer.hpp
  • core/src/protocol/protocol.hpp
  • core/src/protocol/serializer.hpp
  • firmware/c_board/app/src/app.cpp
  • firmware/c_board/app/src/i2c/i2c.cpp
  • firmware/c_board/app/src/i2c/i2c.hpp
  • firmware/c_board/app/src/usb/interrupt_safe_buffer.hpp
  • firmware/c_board/app/src/usb/vendor.hpp
  • firmware/c_board/bsp/cubemx/Core/Inc/i2c.h
  • firmware/c_board/bsp/cubemx/Core/Inc/stm32f4xx_hal_conf.h
  • firmware/c_board/bsp/cubemx/Core/Inc/stm32f4xx_it.h
  • firmware/c_board/bsp/cubemx/Core/Src/dma.c
  • firmware/c_board/bsp/cubemx/Core/Src/gpio.c
  • firmware/c_board/bsp/cubemx/Core/Src/i2c.c
  • firmware/c_board/bsp/cubemx/Core/Src/main.c
  • firmware/c_board/bsp/cubemx/Core/Src/stm32f4xx_it.c
  • firmware/c_board/bsp/cubemx/cmake/stm32cubemx/CMakeLists.txt
  • firmware/c_board/bsp/cubemx/rmcs_slave.ioc
  • host/include/librmcs/agent/c_board.hpp
  • host/include/librmcs/agent/detail/i2c0_common.hpp
  • host/include/librmcs/protocol/handler.hpp
  • host/src/protocol/handler.cpp

Comment on lines +44 to +52
virtual void i2c_write_deserialized_callback(FieldId id, const data::I2cDataView& data) = 0;

virtual void
i2c_read_config_deserialized_callback(FieldId id, const data::I2cReadConfigView& data) = 0;

virtual void
i2c_read_result_deserialized_callback(FieldId id, const data::I2cDataView& data) = 0;

virtual void i2c_error_deserialized_callback(FieldId id, const data::I2cErrorView& data) = 0;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# 检查两个 Vendor 类是否 override 了新加的 4 个 i2c_*_deserialized_callback 方法。
fd -a 'vendor.hpp' firmware | while read -r f; do
  echo "=== $f ==="
  rg -n 'i2c_(write|read_config|read_result|error)_deserialized_callback' "$f" || echo "  (未实现)"
done

Repository: Alliance-Algorithm/librmcs

Length of output: 410


🏁 Script executed:

#!/bin/bash
# 检查 rmcs_board vendor.hpp 的内容和继承关系
echo "=== rmcs_board vendor.hpp 内容 ==="
cat -n firmware/rmcs_board/app/src/usb/vendor.hpp | head -100

echo -e "\n=== rmcs_board Vendor 类定义 ==="
rg -A 5 'class Vendor' firmware/rmcs_board/app/src/usb/vendor.hpp

echo -e "\n=== c_board 四个 callback 实现的内容(示例) ==="
rg -A 8 'i2c_write_deserialized_callback' firmware/c_board/app/src/usb/vendor.hpp | head -20

Repository: Alliance-Algorithm/librmcs

Length of output: 4810


🏁 Script executed:

#!/bin/bash
# 检查是否存在其他继承 DeserializeCallback 的类
echo "=== 所有继承 DeserializeCallback 的类 ==="
rg 'class \w+.*:\s*.*DeserializeCallback' firmware --type hpp --type cpp

Repository: Alliance-Algorithm/librmcs

Length of output: 150


🏁 Script executed:

#!/bin/bash
# 读取 rmcs_board vendor.hpp 的全部内容,确认是否有 i2c 回调实现
wc -l firmware/rmcs_board/app/src/usb/vendor.hpp
echo "=== 完整文件内容 ==="
cat -n firmware/rmcs_board/app/src/usb/vendor.hpp

echo -e "\n=== 检查是否含有 i2c_write_deserialized_callback ==="
grep -n 'i2c_write_deserialized_callback' firmware/rmcs_board/app/src/usb/vendor.hpp || echo "未找到"

Repository: Alliance-Algorithm/librmcs

Length of output: 6532


🏁 Script executed:

#!/bin/bash
# 正确的方式:用 --type-list 查看支持的类型,或直接用 rg 的默认设置
echo "=== 所有继承 DeserializeCallback 的类 ==="
rg 'class \w+.*:\s*.*DeserializeCallback' firmware

Repository: Alliance-Algorithm/librmcs

Length of output: 105


🏁 Script executed:

#!/bin/bash
# 查看 DeserializeCallback 中新增的 i2c 方法签名
echo "=== DeserializeCallback 中的 i2c 方法 ==="
rg -A 2 'i2c_(write|read_config|read_result|error)_deserialized_callback' core/src/protocol/deserializer.hpp

# 检查 rmcs_board 是否有 I2C 支持
echo -e "\n=== rmcs_board 是否有 I2C 支持 ==="
find firmware/rmcs_board -name '*i2c*' -type f 2>/dev/null || echo "(未找到 I2C 文件)"

Repository: Alliance-Algorithm/librmcs

Length of output: 604


🏁 Script executed:

#!/bin/bash
# 检查 c_board vendor.hpp 中 i2c 回调的完整实现
echo "=== c_board i2c_write_deserialized_callback 完整实现 ==="
rg -A 6 'i2c_write_deserialized_callback' firmware/c_board/app/src/usb/vendor.hpp

echo -e "\n=== c_board i2c_read_result_deserialized_callback 完整实现 ==="
rg -A 6 'i2c_read_result_deserialized_callback' firmware/c_board/app/src/usb/vendor.hpp

echo -e "\n=== c_board i2c_error_deserialized_callback 完整实现 ==="
rg -A 6 'i2c_error_deserialized_callback' firmware/c_board/app/src/usb/vendor.hpp

Repository: Alliance-Algorithm/librmcs

Length of output: 1048


firmware/rmcs_board/app/src/usb/vendor.hpp 中缺少四个 I2C 回调实现,导致构建失败。

Vendor 类继承自 DeserializeCallback(私有继承),必须实现所有纯虚方法。firmware/c_board/app/src/usb/vendor.hpp 中的实现已齐全(downlink 写操作路由到 i2c0,读结果和错误帧作为仅上行数据类型被忽略)。但 firmware/rmcs_board/app/src/usb/vendor.hpp 缺失下述四个 override:

  • i2c_write_deserialized_callback
  • i2c_read_config_deserialized_callback
  • i2c_read_result_deserialized_callback
  • i2c_error_deserialized_callback

rmcs_board 没有 I2C 硬件,这些方法应为 no-op(可参考 rmcs_board 现有的 GPIO 或加速度计回调实现),以便解除编译阻塞。

🧰 Tools
🪛 GitHub Actions: Lint

[error] 44-52: Pure virtual functions in DeserializeCallback: i2c_write_deserialized_callback, i2c_read_config_deserialized_callback, i2c_read_result_deserialized_callback, i2c_error_deserialized_callback (unimplemented by Vendor).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core/src/protocol/deserializer.hpp` around lines 44 - 52, Add no-op overrides
for the four missing I2C callbacks in the Vendor class so it fulfills the
DeserializeCallback interface: implement i2c_write_deserialized_callback(FieldId
id, const data::I2cDataView& data) override,
i2c_read_config_deserialized_callback(FieldId id, const data::I2cReadConfigView&
data) override, i2c_read_result_deserialized_callback(FieldId id, const
data::I2cDataView& data) override, and i2c_error_deserialized_callback(FieldId
id, const data::I2cErrorView& data) override in
firmware/rmcs_board/app/src/usb/vendor.hpp; each should have an empty body
(no-op), similar to existing rmcs_board GPIO/accelerometer callbacks, to satisfy
the pure-virtual methods in DeserializeCallback and unblock compilation.

Comment on lines +412 to +419
void recover_active_request_locked() {
abort_dma_if_enabled(hal_i2c_handle_->hdmarx);
abort_dma_if_enabled(hal_i2c_handle_->hdmatx);
core::utility::assert_always(HAL_I2C_DeInit(hal_i2c_handle_) == HAL_OK);
core::utility::assert_always(HAL_I2C_Init(hal_i2c_handle_) == HAL_OK);
clear_stale_irq_sources_locked();
finish_active_request_locked();
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

recover_active_request_locked() 在关中断期间执行 HAL_I2C_DeInit/Init,会放大中断延迟

该函数通过 recover_active_request()recover_timed_out_transfer()consume_deferred_recovery() 被调用时,都处于 InterruptLockGuard 作用域内。HAL_I2C_DeInit + HAL_I2C_Init 涉及多次寄存器写、可能等待 BUSY 位清零,关中断期间执行会阻塞所有其它 ISR(CAN/UART/USB/定时器)。

考虑把真正的 DMA abort + HAL 重初始化移到锁外:锁内只快速"标记为需要恢复 + 状态置 Idle",锁外再做 HAL 重建。error_callback() 已经采用了这种延迟恢复的思路(行 124-128),可以把 try_start_next_request 启动失败分支和超时恢复也统一改走 recovery_pending_ 路径,让所有重型 HAL 操作都在 update() 主循环中(且锁外)执行。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@firmware/c_board/app/src/i2c/i2c.hpp` around lines 412 - 419,
recover_active_request_locked currently performs heavy HAL operations
(abort_dma_if_enabled, HAL_I2C_DeInit/HAL_I2C_Init) while holding
InterruptLockGuard, which blocks other ISRs; change it to only perform quick
locked updates: set recovery_pending_ = true, set internal state to Idle,
clear_stale_irq_sources_locked(), and call finish_active_request_locked() — do
not call abort_dma_if_enabled or HAL_I2C_DeInit/Init inside the lock. Move the
actual DMA abort and HAL reinit into the non-ISR path: have update() perform
abort_dma_if_enabled and HAL_I2C_DeInit/HAL_I2C_Init when recovery_pending_ is
true (mirroring error_callback's deferred recovery pattern at lines ~124-128).
Also modify callers (recover_active_request, recover_timed_out_transfer,
consume_deferred_recovery and the try_start_next_request failure branch) to set
recovery_pending_ instead of performing immediate HAL reinit so all heavy HAL
work occurs unlocked in update().

@gqsdjhh gqsdjhh closed this Apr 19, 2026
@github-project-automation github-project-automation Bot moved this from Todo to Done in RMCS Slave SDK Apr 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant